<?php
require_once (dirname(__DIR__).'/core/CreateUuid.php');
class wechat
{

    /**
     * 授权方面的地址
     */
    const API_URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin';
    const API_COMPONENT_TOKEN_URL = '/component/api_component_token';
    const API_CREATE_PREAUTHCODE_URL = '/component/api_create_preauthcode?component_access_token=';
    const API_QUERY_AUTH_URL = '/component/api_query_auth?component_access_token=';
    const API_AUTHORIZER_TOKEN_URL = '/component/api_authorizer_token?component_access_token=';
    const API_GET_AUTHORIZER_INFO_URL = '/component/api_get_authorizer_info?component_access_token=';
    const API_GET_AUTHORIZER_OPTION_URL = '/component/api_get_authorizer_option?component_access_token=';
    const API_SET_AUTHORIZER_OPTION_URL = '/component/api_set_authorizer_option?component_access_token=';
    const API_REDIRECT = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?';

    //用户接口
    const USER_GET_URL='/user/get?';
    const USER_GET_URL_INFO='/user/info?access_token=';
    const USERS_GET_URL_INFO='/user/info/batchget?access_token=';

    /**
     * @var
     * #微信第三平台appid
     */
    private static $appid;
    /**
     * @var
     * #微信第三平台appid
     */
    private static $appsecret;


    /**
     * 微信第三方ticket值
     * @var
     */
    private $component_verify_ticket;

    /**
     * @var
     * 微信第三方平台token
     */
    private $component_access_token;

    /**
     * @var
     * #授权微信token
     */
    private $authorizer_access_token;

    /**
     * 更新授权公众号的token
     * @var
     */
    public static $authorizer_refresh_token;

    /**
     * 请求第三方$component_access_token
     * @var array
     */
    public $options = [];

    /**
     * 错误日志
     * @var string
     */
    public $error = '';

    /**
     * 微信错误码
     * @var int
     */
    public $errCode = 40001;

    /**
     * 错误日志
     * @var string
     */
    public $errMsg = "no access";

    /**
     * 授权微信公总号在数据里边的id
     * @var int
     */
    private $wx_app_data_id = 0;

    /**
     * 公众号关注的用户id：
     * @var int
     */
    private $company_id = 0;


    /**
     * 初始化操作
     * wecaht constructor.
     */
    public function __construct()
    {
        //以下三个变量，自己去开放平台上管理中心根据实际情况填写。
        $encodingAesKey = Yii::$app->params['wechat']['encodingAesKey'];
        $token = Yii::$app->params['wechat']['token'];
        $appId = Yii::$app->params['wechat']['appid'];

        //获取数据
        $ticket = Cm::$db->one("SELECT component_verify_ticket,appsecret FROM `scrm_weixin_three_account` WHERE appid='{$appId}'");
        if (!$ticket) {
            new Exception('component_verify_ticket不存在！');
        }

        self::$appid = $appId;
        self::$appsecret = $ticket['appsecret'];

        $this->component_verify_ticket = $ticket['component_verify_ticket'];

        $this->options = [
            'component_appid' => self::$appid,
            'component_appsecret' => self::$appsecret,
            'component_verify_ticket'=>$this->component_verify_ticket
        ];

    }



    /**
     * 获取第三方平台令牌（component_access_token）
     * 数据实例：{"component_appid":"appid_value" , "component_appsecret": "appsecret_value",  "component_verify_ticket": "ticket_value"   }
     * @return bool
     */
    public function getComponentAccessToken()
    {
        $cache =Cm::$db->one(
            "SELECT component_access_token, component_access_token_expires  FROM `scrm_weixin_three_account` WHERE appid='".self::$appid."'"
        );

        if ($cache && $cache['component_access_token_expires'] > time()) {
            $this -> component_access_token = $cache['component_access_token'];
            return $cache['component_access_token'];
        }

        $result = $this -> http_post(self::API_URL_PREFIX . self::API_COMPONENT_TOKEN_URL, json_encode($this -> options));

        if ($result) {
            $json = json_decode($result, true);
            if (!$json || !empty($json['errcode'])) {
                $this -> errCode = $json['errcode'];
                $this -> errMsg = $json['errmsg'];
                return false;
            }
            $this -> component_access_token = $json['component_access_token'];
            //写入文件来设置缓存
            if (!$cache) {
                Cm::$db->insert('scrm_weixin_three_account', [
                    'id'=>(new \app\api\core\CreateUuid())->toString(),
                    'component_access_token'=>$this->component_access_token,
                    'appid'=>self::$appid,
                    'component_access_token_expires'=>time()+7100
                ]);
            } else {
                Cm::$db->update('scrm_weixin_three_account', [
                    'component_access_token'=>$this->component_access_token,
                    'component_access_token_expires'=>time()+7100
                ], "appid='".self::$appid."'");
            }
            return $this -> component_access_token;
        }
        return false;
    }

    /**
     * 获取（刷新）授权公众号的令牌
     * 该API用于在授权方令牌（authorizer_access_token）失效时，可用刷新令牌（authorizer_refresh_token）获取新的令牌。
     * @param $authorizer_appid
     * @param $authorizer_refresh_token
     * @return bool
     */
    public function getAuthorizerRefreshToken($authorizer_appid, $authorizer_refresh_token)
    {
        $component_access_token = $this -> getComponentAccessToken();
        if (!$component_access_token) {
            return false;
        }
        $response = [
            'component_appid' => self::$appid,
            'authorizer_appid' => $authorizer_appid,
            'authorizer_refresh_token' => $authorizer_refresh_token
        ];
        $result = $this -> http_post(self::API_URL_PREFIX . self::API_AUTHORIZER_TOKEN_URL . $component_access_token, json_encode($response));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || !empty($json['errcode'])) {
                $this -> errCode = $json['errcode'];
                $this -> errMsg = $json['errmsg'];
                return false;
            }

            $data = Cm::$db->one("SELECT id, authorizer_access_token,authorizer_access_token_time,authorizer_refresh_token FROM `scrm_weixin_account` WHERE accountappid='{$authorizer_appid}'");

            if (!$data) {
                $this->wx_app_data_id = (new \app\api\core\CreateUuid())->toString();
                Cm::$db->insert('scrm_weixin_account', [
                    'id'=>$this->wx_app_data_id,
                    'accountappid'=>$json['authorizer_appid'],
                    'authorizer_access_token'=>$json['authorizer_access_token'],
                    'authorizer_access_token_time'=>time()+(int)$json['expires_in'],
                    'authorizer_refresh_token'=>$authorizer_refresh_token,
                ]);
            } else {
                $this->wx_app_data_id  = $data['id'];
                Cm::$db->update('scrm_weixin_account', [
                    'authorizer_access_token'=>$json['authorizer_access_token'],
                    'authorizer_access_token_time'=>time()+(int)$json['expires_in'],
                ], "accountappid='{$authorizer_appid}'");
            }
            $this -> authorizer_access_token = $json['authorizer_access_token'];

            return $this -> authorizer_access_token;
        }
        return false;
    }

    /**
     * 获取授权公众号的authorizer_access_token
     * @param $authorizer_appid
     * @return bool
     */
    public function getAuthorizerAccessToken($authorizer_appid)
    {

        $cache = Cm::$db->one("SELECT id, authorizer_access_token,authorizer_access_token_time,authorizer_refresh_token FROM `scrm_weixin_account` WHERE accountappid='{$authorizer_appid}'");
        $authorizer_access_token = '';

        if ($cache) {
            $this->wx_app_data_id = $cache['id'];
            if ((int)$cache['authorizer_access_token_time'] > time()) {
                $authorizer_access_token = $cache['authorizer_access_token'];
            } else {
                $authorizer_refresh_token = $cache['authorizer_refresh_token'];

                $authorizer_access_token = $this -> getAuthorizerRefreshToken($authorizer_appid, $authorizer_refresh_token);
            }
        }

        $this->getCompanyId($this->company_id);
        $this->authorizer_access_token = $authorizer_access_token;

        return $this->authorizer_access_token;
    }

    /**
     * 权限分配：
     * @param $appid
     * @param $fun
     */
    public function getAuthInfo($authorizer_appid,$fun=0)
    {
        $cache = Cm::$db->one("SELECT status, func_info,accountverifytype FROM `scrm_weixin_account` WHERE accountappid='{$authorizer_appid}'");
        if (!$cache) {
            $this->error = "公众号appi:{$authorizer_appid}未找到！";
            return false;
        } else {
            if ((int)$cache['status']===0) {
                $this->error = "公众号appi:{$authorizer_appid}未授权或已取消授权！";
                return false;
            }
            /**
             * 1、微博认证视作未认证,因此微博认证的公众号不会拥有微信认证公众号特有的接口。
             * 2、微信认证分为资质认证和名称认证两部分，只需要资质认证通过，就可获得接口。
             * $verifyType:0未认证， 1已认证
             */
            $verifyType = in_array((int)$cache['accountverifytype'], [-1, 1, 2]) ? 0 : 1;
            if ((int)$verifyType===0) {
                $this->error = "公众号appi:{$authorizer_appid}未认证！";
                return false;
            }
            $funInfo = explode(',', $cache['func_info']);
            if ( $fun && !in_array($fun, $funInfo)) {
                $this->error = "公众号appi:{$authorizer_appid}未授权！";
                return false;
            }
        }
        return true;
    }

    public function getCompanyId($wx_account_id)
    {
        $cache = Cm::$db->one("SELECT company_id FROM `scrm_weixin_company_account` WHERE wx_account_id='{$wx_account_id}'");
        if (!$cache && isset($cache['company_id'])) {
            $this->company_id = $cache['company_id'];
        }

        return $this->company_id;
    }

    /**
     * 记录日志
     */
    public function __destruct()
    {
        if (!empty($this->error)) {
            $msg = !empty($this->errCode) ? "，微信错误码：{$this->errCode}错误日志:{$this->errMsg}" : '';
            YII_DEBUG && self::logResult('/log-debug.log','详细错误：'.$this->error.$msg);
        }
    }

    /**
     * 写日志
     * @param $path
     * @param $data
     */
    public static function logResult($path, $data)
    {

        if (!is_dir(LOGPATH)) {
            mkdir(LOGPATH, 0777, true);
        }

        file_put_contents(LOGPATH.$path, '[时间：'.date('Y-m-d  h:i:i',time()).']，结果：'.$data."\r\n",FILE_APPEND);
    }

    /**
     * 批量获取关注用户列表
     * @param $authorizer_appid:微信公总号、appid
     * @param $next_openid：最后一个用户的openid
     * @return bool|mixed
     */
    public function getUserList ($authorizer_appid, $next_openid='')
    {

        if (!$this->getAuthInfo($authorizer_appid)) {
            return false;
        }

        $authorizer_access_token = $this->getAuthorizerAccessToken($authorizer_appid);
        if (!$authorizer_access_token) {
            return false;
        }
        if($next_openid) {
            $result = $this->http_get(self::API_URL_PREFIX.self::USER_GET_URL.'access_token='.$authorizer_access_token.'&next_openid='.$next_openid);
        } else {
            $result = $this->http_get(self::API_URL_PREFIX.self::USER_GET_URL.'access_token='.$authorizer_access_token);
        }
        if ($result) {
            $json = json_decode($result,true);
            if (isset($json['errcode'])) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                return false;
            }

            $nextOpenid = $json['next_openid'];
            $data = $json['data']['openid'];

            $total = (int)$json['total'];#粉丝总人数
            $count = (int)$json['count'];#当前粉丝数

            $userCount = count($data);

            //批量获取用户数据
            if ($userCount < 100) {
                return $this->getBatchUsersInfo($authorizer_appid, $data);
            } else {
                $i = (int)ceil($userCount/100);
                $k = 0;
                while ($k < $i) {
                    $star_index = $k * 100;
                    $end_index = $star_index+(100-1);
                    if ($end_index > $count) {
                        $end_index = $count;
                    }
                    $temp = array_slice($data, $star_index, $end_index);
                    $this->getBatchUsersInfo($authorizer_appid, $temp);
                    $k++;
                }
            }

            //一直取数据
            if (!empty($nextOpenid)) {
                 return $this->getUserList($authorizer_appid, $nextOpenid);
            }
        }
        return true;
    }

    /**
     * 获取单个用户信息
     * @param $authorizer_appid
     * @param $openid
     * @param bool $insert
     * @return array|bool|false
     */
    public function getUserInfo($authorizer_appid, $openid, $insert=false)
    {
        if (!$this->getAuthInfo($authorizer_appid)) {
            return false;
        }

        $authorizer_access_token = $this->getAuthorizerAccessToken($authorizer_appid);
        if (!$authorizer_access_token) {
            return false;
        }

        $result = $this->http_get(self::API_URL_PREFIX.self::USER_GET_URL_INFO.$authorizer_access_token.'&openid='.$openid);
        if ($result) {
            $json = json_decode($result,true);
            if (isset($json['errcode'])) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                $this->error = $json['errmsg'];
                return false;
            }
            if ($insert) {
                $data = Cm::$db->one("select id from `scrm_weixin_customer` WHERE openid='{$openid}' and wx_account_id='{$this->wx_app_data_id }'");
                if ((int)$json['subscribe']) {
                    $user = [
                        'openid'=>$json['openid'],
                        'unionid'=>isset($json['unionid']) ? $json['unionid'] : '',
                        'subscribe'=>$json['subscribe_time'],
                        'unsubscribe'=>0,
                        'status'=>(int)$json['subscribe'],
                        'nick_name'=>$json['nickname'],
                        'sex'=>(int)$json['sex'],
                        'city'=>$json['city'],
                        'province'=>$json['province'],
                        'country'=>$json['country'],
                        'headimgurl'=>$json['headimgurl'],
                        //'last_time'=>$json[''],
                        //'qrcode'=>$json[''],
                        'groupid'=>$json['groupid'],
                        'remark'=>$json['remark'],
                        'tagid_list'=>!empty($json['tagid_list']) ? implode(',', $json['tagid_list']) : '',
                        'subscribe_scene'=>$json['subscribe_scene'],
                        'subscribe_scene_type'=>self::getSubscribeScene($json['subscribe_scene']),
                        'company_id'=>$this->company_id,
                        'qr_scene'=>$json['qr_scene'],
                        'qr_scene_str'=>$json['qr_scene_str'],

                    ];
                } else {
                    $user = [
                        'openid'=>$json['openid'],
                        'status'=>0,
                    ];
                }

                if (!$data) {
                    $user['date_added']=date('Y-m-d H:i:s');
                    $user['wx_account_id']=$this->wx_app_data_id ;
                    $user['id']=(new \app\api\core\CreateUuid())->toString();
                    Cm::$db->insert('scrm_weixin_customer', $user);
                } else {
                    Cm::$db->update('scrm_weixin_customer', $user, "openid='{$openid}' and wx_account_id='{$this->wx_app_data_id }'");
                }
            }

            $resultData = Cm::$db->one("select * from `scrm_weixin_customer` WHERE openid='{$openid}' and wx_account_id='{$this->wx_app_data_id }'");
            return $resultData;
        }
        $this->error = '微信接口调用失败';
        return false;
    }

    /**
     * 获取批量用户信息
     * @param $authorizer_appid
     * @param array $openids
     * @return bool
     */
    public function getBatchUsersInfo($authorizer_appid, $openids = [])
    {
        $authorizer_access_token = $this->getAuthorizerAccessToken($authorizer_appid);
        if (!$authorizer_access_token) {
            return false;
        }

        $list = [];
        if (is_array($openids) && !empty($openids)) {
            foreach ($openids as $item) {
                $list[] = [
                    'lang'=>'zh_CN',
                    'openid'=>$item,
                ];
            }
            $jsonParams = json_encode(['user_list'=>$list]);
            $result = $this->http_post(self::API_URL_PREFIX.self::USERS_GET_URL_INFO.$authorizer_access_token, $jsonParams);
            if ($result) {
                $json = json_decode($result,true);
                if (isset($json['errcode'])) {
                    $this->errCode = $json['errcode'];
                    $this->errMsg = $json['errmsg'];
                    $this->error = $json['errmsg'];
                    return false;
                }

                $data = isset($json['user_info_list']) ? $json['user_info_list'] : [];
                if (!empty($data)) {
                    foreach ($data as $item) {
                        $resultData = Cm::$db->one("select id from `scrm_weixin_customer` WHERE openid='{$item['openid']}' and wx_account_id='{$this->wx_app_data_id }'");

                        if ((int)$item['subscribe'] > 0) {
                            $user = [
                                'openid'=>$item['openid'],
                                'unionid'=>isset($item['unionid']) ? $item['unionid'] : '',
                                'subscribe'=>date('Y-m-d H:i:s', $item['subscribe_time']),
                                //'unsubscribe'=>0,
                                'status'=>(int)$item['subscribe'],
                                'nick_name'=>$item['nickname'],
                                'sex'=>(int)$item['sex'],
                                'city'=>$item['city'],
                                'province'=>$item['province'],
                                'country'=>$item['country'],
                                'headimgurl'=>$item['headimgurl'],
                                //'last_time'=>$item[''],
                                //'qrcode'=>$item[''],
                                'groupid'=>$item['groupid'],
                                'remark'=>$item['remark'],
                                'tagid_list'=>!empty($item['tagid_list']) ? implode(',', $item['tagid_list']) : '',
                                'subscribe_scene'=>$item['subscribe_scene'],
                                'subscribe_scene_type'=>self::getSubscribeScene($item['subscribe_scene']),
                                'company_id'=>$this->company_id,
                                'qr_scene'=>$item['qr_scene'],
                                'qr_scene_str'=>$item['qr_scene_str'],


                            ];
                        } else {
                            $user = [
                                'openid'=>$item['openid'],
                                'status'=>0,
                            ];
                        }

                        if (!$resultData) {
                            $user['date_added']=date('Y-m-d H:i:s');
                            $user['wx_account_id']=$this->wx_app_data_id ;
                            $user['id']=(new \app\api\core\CreateUuid())->toString();
                            Cm::$db->insert('scrm_weixin_customer', $user);
                        } else {
                            Cm::$db->update('scrm_weixin_customer', $user, "openid='{$item['openid']}' and wx_account_id='{$this->wx_app_data_id}'");
                        }
                    }
                }

            }
        }
        return true;
    }

    /**
     * 获取用户关注公总号来源
     * @param $subscribe_scene
     * @return int
     */
    public static function getSubscribeScene($subscribe_scene)
    {
        switch (trim($subscribe_scene)) {
            case 'ADD_SCENE_SEARCH':#公众号搜索
                $type = 0;
                break;
            case 'ADD_SCENE_ACCOUNT_MIGRATION':# 公众号迁移
                $type = 1;
                break;
            case 'ADD_SCENE_PROFILE_CARD':#名片分享
                $type = 2;
                break;
            case 'ADD_SCENEPROFILE LINK':#图文页内名称点击
                $type = 3;
                break;
            case 'ADD_SCENE_QR_CODE':#扫描二维码
                $type = 4;
                break;
            case 'ADD_SCENE_PROFILE_ITEM':#图文页右上角菜单
                $type = 5;
                break;
            case 'ADD_SCENE_PAID':#支付后关注
                $type = 6;
                break;
            default:
                $type = 7;
                break;
        }
        return $type;
    }

    /**
     * GET 请求
     * @param string $url
     */
    private function http_get($url)
    {
        $oCurl = curl_init();
        if (stripos($url, "https://") !== FALSE) {
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
        }
        curl_setopt($oCurl, CURLOPT_URL, $url);
        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
        $sContent = curl_exec($oCurl);
        $aStatus = curl_getinfo($oCurl);
        curl_close($oCurl);
        if (intval($aStatus["http_code"]) == 200) {
            return $sContent;
        } else {
            return false;
        }
    }

    /**
     * POST 请求
     * @param string $url
     * @param array $param
     * @return string content
     */
    public function http_post($url, $param)
    {
        $oCurl = curl_init();
        if (stripos($url, "https://") !== FALSE) {
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
        }
        if (is_string($param)) {
            $strPOST = $param;
        } else {
            $aPOST = array();
            foreach ($param as $key => $val) {
                $aPOST[] = $key . "=" . urlencode($val);
            }
            $strPOST = join("&", $aPOST);
        }
        //file_put_contents('./mylog.txt',$url."\r\n".$strPOST."\r\n".date('Y-m-d H:i:s')."\r\n==============\r\n",FILE_APPEND);
        curl_setopt($oCurl, CURLOPT_URL, $url);
        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($oCurl, CURLOPT_POST, true);
        curl_setopt($oCurl, CURLOPT_POSTFIELDS, $strPOST);
        $sContent = curl_exec($oCurl);
        $aStatus = curl_getinfo($oCurl);
        curl_close($oCurl);
        if (intval($aStatus["http_code"]) == 200) {
            return $sContent;
        } else {
            return false;
        }
    }
    /**
     * 微信api不支持中文转义的json结构
     * @param array $arr
     */
    static function json_encode($arr) {
        $parts = array();
        $is_list = false;
        //Find out if the given array is a numerical array
        $keys = array_keys($arr);
        $max_length = count($arr) - 1;
        if (($keys[0] === 0) && ($keys[$max_length] === $max_length)) {//See if the first key is 0 and last key is length - 1
            $is_list = true;
            for ($i = 0; $i < count($keys); $i++) {//See if each key correspondes to its position
                if ($i != $keys[$i]) {//A key fails at position check.
                    $is_list = false;
                    //It is an associative array.
                    break;
                }
            }
        }
        foreach ($arr as $key => $value) {
            if (is_array($value)) {//Custom handling for arrays
                if ($is_list)
                    $parts[] = self::json_encode($value);
                /* :RECURSION: */
                else
                    $parts[] = '"' . $key . '":' . self::json_encode($value);
                /* :RECURSION: */
            } else {
                $str = '';
                if (!$is_list)
                    $str = '"' . $key . '":';
                //Custom handling for multiple data types
                if (is_numeric($value) && $value < 2000000000)
                    $str .= $value;
                //Numbers
                elseif ($value === false)
                    $str .= 'false';
                //The booleans
                elseif ($value === true)
                    $str .= 'true';
                else
                    $str .= '"' . addslashes($value) . '"';
                //All other things
                // :TODO: Is there any more datatype we should be in the lookout for? (Object?)
                $parts[] = $str;
            }
        }
        $json = implode(',', $parts);
        if ($is_list)
            return '[' . $json . ']';
        //Return numerical JSON
        return '{' . $json . '}';
        //Return associative JSON

    }

}